import type IApplication from '@candy/framework/core/IApplication';
import type HttpResponse from '@candy/framework/http/HttpResponse';
import type IException from '@candy/framework/core/IException';
import HttpRequest, { type ConnectionInfo } from '@candy/framework/http/HttpRequest';
import Hook from '@candy/framework/core/Hook';

type BunServeOptions = {
    fetch: (req: Request, server: BunServer) => Promise<Response>;
    hostname?: string;
    port?: number;
    development?: boolean;
    error?: (error: Error) => Response | Promise<Response>;
    tls?: {
        key?: string;
        cert?: string;
        ca?: string;
        passphrase?: string;
        dhParamsFile?: string;
    };
    maxRequestBodySize?: number;
    lowMemoryMode?: boolean;
};
type Bun = {
    serve(options: BunServeOptions): BunServer;
};

type BunServer = {
    development: boolean;
    hostname: string;
    port: number;
    pendingRequests: number;
    requestIP(req: Request): SocketAddress | null;
    stop(): void;
};

type SocketAddress = {
    address: string;
    port: number;
    family: 'IPv4' | 'IPv6';
};

declare const Bun: Bun;

/**
 * Framework Entry
 */
export default class Main {
    public application: IApplication;

    constructor(app: IApplication) {
        this.application = app;
    }

    private async requestListener(req: Request, connectionInfo: ConnectionInfo): Promise<Response> {
        let res: HttpResponse;

        try {
            const hookData = await new Hook(req).run();
            if (null !== hookData) {
                return hookData;
            }

            const httpRequest = new HttpRequest(req, connectionInfo);
            res = await this.application.requestListener(httpRequest);
        } catch (e) {
            res = this.application.handlerException(e as IException);
        }

        return res.toResponse();
    }

    public listen(options: Omit<BunServeOptions, 'fetch'>): void {
        // deno-lint-ignore no-this-alias
        const mod = this;
        Bun.serve({
            async fetch(req: Request, server: BunServer): Promise<Response> {
                const client = server.requestIP(req);
                return await mod.requestListener(req, {
                    encrypted: options.tls !== undefined && options.tls.key !== undefined,
                    info: {
                        remoteAddr: {
                            hostname: client?.address ?? '',
                            port: client?.port ?? 0,
                        },
                    },
                });
            },
            ...options,
        });
    }
}
